/*
 * Copyright (C) 2010 Fred Barrie This program is free software: you can
 * redistribute it and/or modify it under the terms of the GNU General Public
 * License as published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version. This program is distributed
 * in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
 * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU General Public License for more details. You should have received
 * a copy of the GNU General Public License along with this program. If not, see
 * <http://www.gnu.org/licenses/>.
 */
package org.aergia.vinny.gui.util;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dialog.ModalExclusionType;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
import java.util.concurrent.CountDownLatch;

import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JPasswordField;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import javax.swing.WindowConstants;

import org.aergia.vinny.engine.VinnyConfiguration;

/**
 * @author fred
 */
public class InitializeDatabase extends JFrame implements ActionListener {

	private enum STATE {

		INVALID_ROOT_USER,

		NO_SERVER,

		NO_VINNY_DATABASE,

		NO_VINNY_USER,

		OK,

		UNKNOWN

	}

	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;

	public static void main(final String[] args) throws InterruptedException {
		final VinnyConfiguration cfg = new VinnyConfiguration();
		cfg.loadConfigurationFile("~/.vinny/conf/cfg.xml");

		final CountDownLatch latch = new CountDownLatch(1);
		final InitializeDatabase frame = new InitializeDatabase(cfg, latch);
		frame.setVisible(true);
		latch.await();
		System.exit(0);
	}

	private final VinnyConfiguration cfg;
	private JComboBox cfgList;
	private JComboBox currentState;
	private JComboBox databaseList;
	private JTextField databaseName;
	private JButton doneButton;
	private final CountDownLatch latch;
	private JTextField portNumber;
	private JTextField rootPassword;
	private JTextField rootUserName;
	private JTextField serverName;
	private STATE state;
	private JTextField vinnyPassword;
	private JTextField vinnyUserName;

	public InitializeDatabase(final VinnyConfiguration cfg, final CountDownLatch latch) {
		this.cfg = cfg;
		this.latch = latch;
		this.initComponents();
	}

	@Override
	public void actionPerformed(final ActionEvent e) {

		if (e.getSource() == this.currentState) {

			SwingUtilities.invokeLater(new Runnable() {

				@SuppressWarnings("synthetic-access")
				@Override
				public void run() {
					InitializeDatabase.this.state = (STATE) InitializeDatabase.this.currentState.getSelectedItem();
					InitializeDatabase.this.updateState();
				}
			});
		}

		if (e.getSource() == this.cfgList) {
			this.setValues();
		}

		if (e.getSource() == this.rootPassword) {
			this.state = STATE.UNKNOWN;
			this.updateState();
		}

		if (e.getSource() == this.doneButton) {

			final String name = (String) this.cfgList.getSelectedItem();
			this.cfg.setDatabaseName(name, this.databaseName.getText());
			this.cfg.setServerName(name, this.serverName.getText());
			this.cfg.setUserName(name, this.vinnyUserName.getText());
			this.cfg.setPassword(name, this.vinnyPassword.getText());
			try {
				this.cfg.setPortNumber(name, Integer.parseInt(this.portNumber.getText()));
			} catch (final NumberFormatException nfe) {
				this.cfg.setPortNumber(name, 3306);
			}
			this.latch.countDown();
		}

		if (e.getSource() == this.currentState) {
			this.state = (STATE) this.currentState.getSelectedItem();
			SwingUtilities.invokeLater(new Runnable() {
				@SuppressWarnings("synthetic-access")
				@Override
				public void run() {
					InitializeDatabase.this.updateState();
				}
			});
		}
	}

	private Connection getConnection(final String user, final String password, final String database) throws SQLException {
		Connection conn = null;
		final Properties connectionProps = new Properties();
		connectionProps.put("user", user);
		connectionProps.put("password", password);

		if (this.databaseList.getSelectedItem().equals("MySQL")) {
			conn = DriverManager.getConnection("jdbc:mysql://" + this.serverName.getText() + "/" + database, connectionProps);
		} else if (this.databaseList.getSelectedItem().equals("Derby")) {
			conn = DriverManager.getConnection("jdbc:derby:" + database + ";create=true", connectionProps);
		}
		return conn;
	}

	private void initComponents() {
		this.setTitle("Database Configuration");
		this.setModalExclusionType(ModalExclusionType.APPLICATION_EXCLUDE);
		this.setAlwaysOnTop(true);
		this.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
		this.setLayout(new BorderLayout());

		this.currentState = new JComboBox(STATE.values());
		this.currentState.setName("Current State");
		this.currentState.addActionListener(this);

		this.cfgList = new JComboBox(this.cfg.getPersistenceUnitNames());
		this.cfgList.setName("Persistence Unit");
		this.cfgList.addActionListener(this);

		this.cfgList.getSelectedItem();

		final String[] list = { "MySQL" };
		this.databaseList = new JComboBox(list);
		this.databaseList.setName("Database Server");
		this.databaseList.setEditable(false);
		this.databaseList.setVisible(false);

		this.serverName = new JTextField();
		this.serverName.setName("Server Name");

		this.portNumber = new JTextField();
		this.portNumber.setName("Port Number");
		this.portNumber.setVisible(false);

		this.databaseName = new JTextField();
		this.databaseName.setName("Database Name");
		this.databaseName.setVisible(false);

		this.rootUserName = new JTextField("root");
		this.rootUserName.setName("Root User Name");
		this.rootUserName.setVisible(false);

		this.rootPassword = new JPasswordField("password");
		this.rootPassword.setName("Root Password");
		this.rootPassword.addActionListener(this);
		this.rootPassword.addKeyListener(new KeyListener() {

			@Override
			public void keyPressed(final KeyEvent e) {
				// ignore
			}

			@Override
			public void keyReleased(final KeyEvent e) {
				// ignore
			}

			@Override
			public void keyTyped(final KeyEvent e) {
				SwingUtilities.invokeLater(new Runnable() {

					@SuppressWarnings("synthetic-access")
					@Override
					public void run() {
						InitializeDatabase.this.state = (STATE) InitializeDatabase.this.currentState.getSelectedItem();
						InitializeDatabase.this.updateState();
					}
				});
			}
		});

		this.vinnyUserName = new JTextField();
		this.vinnyUserName.setName("App User Name");
		this.vinnyUserName.setVisible(false);

		this.vinnyPassword = new JTextField();
		this.vinnyPassword.setName("App Password");
		this.vinnyPassword.setVisible(false);

		// this.add(this.currentState, BorderLayout.NORTH);
		this.add(new JTextArea("Please enter the MySQL database server name.  If the database\n"
				+ "is running on this computer, then the default localhost is correct.\n" + "\n"
				+ "Please enter the root password that was used when installing\n" + "the database server.\n"), BorderLayout.NORTH);

		final LabelPanel panel = new LabelPanel(FontUtil.getLabelFont(), Color.BLACK, this.databaseList, this.serverName, this.portNumber, this.databaseName,
				this.vinnyUserName, this.vinnyPassword, this.rootUserName, this.rootPassword);

		this.add(panel, BorderLayout.CENTER);

		this.doneButton = new JButton("Done");
		this.doneButton.addActionListener(this);
		this.add(this.doneButton, BorderLayout.SOUTH);
		this.doneButton.setEnabled(false);
		this.pack();

		this.state = STATE.UNKNOWN;

		this.setValues();
		this.updateState();
	}

	private void setValues() {
		final String persistenceUnit = (String) this.cfgList.getSelectedItem();
		this.currentState.setSelectedItem(STATE.UNKNOWN);
		this.databaseName.setText(this.cfg.getDatabaseName(persistenceUnit));
		this.serverName.setText(this.cfg.getServerName(persistenceUnit));
		this.vinnyPassword.setText(this.cfg.getUserPassword(persistenceUnit));
		this.vinnyUserName.setText(this.cfg.getUserName(persistenceUnit));
		this.portNumber.setText(Integer.toString(this.cfg.getPortNumber(persistenceUnit)));
	}

	private void tryAndConnectToServer() {
		try {
			this.getConnection(this.vinnyUserName.getText(), this.vinnyPassword.getText(), this.databaseName.getText());
			this.state = STATE.OK;
		} catch (final SQLException se) {
			final String sqlState = se.getSQLState();
			if (sqlState.equals("08S01")) {
				this.state = STATE.NO_SERVER;
			} else if (sqlState.equals("28000")) {
				this.state = STATE.NO_VINNY_DATABASE;
			} else {
				this.state = STATE.NO_VINNY_DATABASE;
			}
		}
	}

	private void tryAndCreateVinnyDatabase() {
		try {
			final Connection con = this.getConnection(this.rootUserName.getText(), this.rootPassword.getText(), "");
			final Statement stmt = con.createStatement();
			stmt.execute("create database " + this.databaseName.getText());
			stmt.close();
			con.close();
			this.state = STATE.NO_VINNY_USER;
		} catch (final SQLException se) {
			final String sqlState = se.getSQLState();
			if (sqlState.equals("28000")) {
				this.state = STATE.INVALID_ROOT_USER;
			} else if (sqlState.equals("HY000")) {
				this.state = STATE.NO_VINNY_USER;
			}
		}
	}

	private void tryAndCreateVinnyUser() {
		try {
			final Connection con = this.getConnection(this.rootUserName.getText(), this.rootPassword.getText(), this.databaseName.getText());
			final Statement stmt = con.createStatement();
			stmt.execute("create user '" + this.vinnyUserName.getText() + "' identified by '" + this.vinnyPassword.getText() + "'");
			stmt.execute("grant all on " + this.databaseName.getText() + ".* to '" + this.vinnyUserName.getText() + "'@'%'");
			stmt.close();
			con.close();
			this.state = STATE.UNKNOWN;
		} catch (final SQLException se) {
			final String sqlState = se.getSQLState();
			if (sqlState.equals("28000")) {
				this.state = STATE.INVALID_ROOT_USER;
			} else if (sqlState.equals("HY000")) {
				this.state = STATE.OK;
			}
		}
	}

	private void updateState() {
		if ((this.state == STATE.UNKNOWN) || (this.state == STATE.NO_SERVER)) {
			this.tryAndConnectToServer();
		}
		if (this.state == STATE.NO_VINNY_DATABASE) {
			this.tryAndCreateVinnyDatabase();
		}
		if (this.state == STATE.NO_VINNY_USER) {
			this.tryAndCreateVinnyUser();
		}
		if (this.state != this.currentState.getSelectedItem()) {
			this.currentState.setSelectedItem(this.state);
		}
		if (this.state == STATE.OK) {
			this.doneButton.setEnabled(true);
		}
	}
}
